agora inbox for pgsql-hackers@postgresql.org  
help / color / mirror / Atom feed
[PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
461+ messages / 2 participants
[nested] [flat]

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 461+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



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

* [PATCH v3 1/4] pg_dump/pg_dumpall: bump minimum supported version to v10
@ 2026-04-17 17:15 Nathan Bossart <nathan@postgresql.org>
  0 siblings, 0 replies; 461+ messages in thread

From: Nathan Bossart @ 2026-04-17 17:15 UTC (permalink / raw)

---
 doc/src/sgml/ref/pg_dump.sgml |  14 +-
 doc/src/sgml/runtime.sgml     |   2 +-
 src/bin/pg_dump/pg_dump.c     | 330 ++--------------------------------
 src/bin/pg_dump/pg_dumpall.c  |  32 +---
 4 files changed, 22 insertions(+), 356 deletions(-)

diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml
index ae1bc14d2f2..64a3447e445 100644
--- a/doc/src/sgml/ref/pg_dump.sgml
+++ b/doc/src/sgml/ref/pg_dump.sgml
@@ -407,18 +407,6 @@ PostgreSQL documentation
         and there is no way to continue with the dump, so <application>pg_dump</application> has no choice
         but to abort the dump.
        </para>
-       <para>
-        To perform a parallel dump, the database server needs to support
-        synchronized snapshots, a feature that was introduced in
-        <productname>PostgreSQL</productname> 9.2 for primary servers and 10
-        for standbys. With this feature, database clients can ensure they see
-        the same data set even though they use different connections.
-        <command>pg_dump -j</command> uses multiple database connections; it
-        connects to the database once with the leader process and once again
-        for each worker job. Without the synchronized snapshot feature, the
-        different worker jobs wouldn't be guaranteed to see the same data in
-        each connection, which could lead to an inconsistent backup.
-       </para>
       </listitem>
      </varlistentry>
 
@@ -1718,7 +1706,7 @@ CREATE DATABASE foo WITH TEMPLATE template0;
    <productname>PostgreSQL</productname> server versions newer than
    <application>pg_dump</application>'s version.  <application>pg_dump</application> can also
    dump from <productname>PostgreSQL</productname> servers older than its own version.
-   (Currently, servers back to version 9.2 are supported.)
+   (Currently, servers back to version 10 are supported.)
    However, <application>pg_dump</application> cannot dump from
    <productname>PostgreSQL</productname> servers newer than its own major version;
    it will refuse to even try, rather than risk making an invalid dump.
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index dfa292c2c3a..d9984910cc4 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -1776,7 +1776,7 @@ $ <userinput>kill -INT `head -1 /usr/local/pgsql/data/postmaster.pid`</userinput
     version of
     <productname>PostgreSQL</productname>, to take advantage of enhancements
     that might have been made in these programs.  Current releases of the
-    dump programs can read data from any server version back to 9.2.
+    dump programs can read data from any server version back to 10.
    </para>
 
    <para>
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index d56dcc701ce..eed9aaeb7c1 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -977,10 +977,10 @@ main(int argc, char **argv)
 
 
 	/*
-	 * We allow the server to be back to 9.2, and up to any minor release of
+	 * We allow the server to be back to 10, and up to any minor release of
 	 * our own major version.  (See also version check in pg_dumpall.c.)
 	 */
-	fout->minRemoteVersion = 90200;
+	fout->minRemoteVersion = 100000;
 	fout->maxRemoteVersion = (PG_VERSION_NUM / 100) * 100 + 99;
 
 	fout->numWorkers = numWorkers;
@@ -1491,9 +1491,7 @@ setup_connection(Archive *AH, const char *dumpencoding,
 	 * Disable timeouts if supported.
 	 */
 	ExecuteSqlStatement(AH, "SET statement_timeout = 0");
-	if (AH->remoteVersion >= 90300)
 		ExecuteSqlStatement(AH, "SET lock_timeout = 0");
-	if (AH->remoteVersion >= 90600)
 		ExecuteSqlStatement(AH, "SET idle_in_transaction_session_timeout = 0");
 	if (AH->remoteVersion >= 170000)
 		ExecuteSqlStatement(AH, "SET transaction_timeout = 0");
@@ -1507,13 +1505,10 @@ setup_connection(Archive *AH, const char *dumpencoding,
 	/*
 	 * Adjust row-security mode, if supported.
 	 */
-	if (AH->remoteVersion >= 90500)
-	{
 		if (dopt->enable_row_security)
 			ExecuteSqlStatement(AH, "SET row_security = on");
 		else
 			ExecuteSqlStatement(AH, "SET row_security = off");
-	}
 
 	/*
 	 * For security reasons, we restrict the expansion of non-system views and
@@ -1568,11 +1563,7 @@ setup_connection(Archive *AH, const char *dumpencoding,
 		destroyPQExpBuffer(query);
 	}
 	else if (AH->numWorkers > 1)
-	{
-		if (AH->isStandby && AH->remoteVersion < 100000)
-			pg_fatal("parallel dumps from standby servers are not supported by this server version");
 		AH->sync_snapshot_id = get_synchronized_snapshot(AH);
-	}
 }
 
 /* Set up connection for a parallel worker process */
@@ -1964,12 +1955,7 @@ checkExtensionMembership(DumpableObject *dobj, Archive *fout)
 	if (fout->dopt->binary_upgrade)
 		dobj->dump = ext->dobj.dump;
 	else
-	{
-		if (fout->remoteVersion < 90600)
-			dobj->dump = DUMP_COMPONENT_NONE;
-		else
 			dobj->dump = ext->dobj.dump_contains & (DUMP_COMPONENT_ACL);
-	}
 
 	return true;
 }
@@ -2000,11 +1986,10 @@ selectDumpableNamespace(NamespaceInfo *nsinfo, Archive *fout)
 			simple_oid_list_member(&schema_include_oids,
 								   nsinfo->dobj.catId.oid) ?
 			DUMP_COMPONENT_ALL : DUMP_COMPONENT_NONE;
-	else if (fout->remoteVersion >= 90600 &&
-			 strcmp(nsinfo->dobj.name, "pg_catalog") == 0)
+	else if (strcmp(nsinfo->dobj.name, "pg_catalog") == 0)
 	{
 		/*
-		 * In 9.6 and above, we dump out any ACLs defined in pg_catalog, if
+		 * We dump out any ACLs defined in pg_catalog, if
 		 * they are interesting (and not the original ACLs which were set at
 		 * initdb time, see pg_init_privs).
 		 */
@@ -2213,8 +2198,7 @@ selectDumpableProcLang(ProcLangInfo *plang, Archive *fout)
 	else
 	{
 		if (plang->dobj.catId.oid <= g_last_builtin_oid)
-			plang->dobj.dump = fout->remoteVersion < 90600 ?
-				DUMP_COMPONENT_NONE : DUMP_COMPONENT_ACL;
+			plang->dobj.dump = DUMP_COMPONENT_ACL;
 		else
 			plang->dobj.dump = DUMP_COMPONENT_ALL;
 	}
@@ -2231,13 +2215,6 @@ selectDumpableProcLang(ProcLangInfo *plang, Archive *fout)
 static void
 selectDumpableAccessMethod(AccessMethodInfo *method, Archive *fout)
 {
-	/* see getAccessMethods() comment about v9.6. */
-	if (fout->remoteVersion < 90600)
-	{
-		method->dobj.dump = DUMP_COMPONENT_NONE;
-		return;
-	}
-
 	if (checkExtensionMembership(&method->dobj, fout))
 		return;					/* extension membership overrides all else */
 
@@ -3123,10 +3100,6 @@ buildMatViewRefreshDependencies(Archive *fout)
 				i_objid,
 				i_refobjid;
 
-	/* No Mat Views before 9.3. */
-	if (fout->remoteVersion < 90300)
-		return;
-
 	query = createPQExpBuffer();
 
 	appendPQExpBufferStr(query, "WITH RECURSIVE w AS "
@@ -3325,10 +3298,7 @@ dumpDatabase(Archive *fout)
 						 "datcollate, datctype, datfrozenxid, "
 						 "datacl, acldefault('d', datdba) AS acldefault, "
 						 "datistemplate, datconnlimit, ");
-	if (fout->remoteVersion >= 90300)
 		appendPQExpBufferStr(dbQry, "datminmxid, ");
-	else
-		appendPQExpBufferStr(dbQry, "0 AS datminmxid, ");
 	if (fout->remoteVersion >= 170000)
 		appendPQExpBufferStr(dbQry, "datlocprovider, datlocale, datcollversion, ");
 	else if (fout->remoteVersion >= 150000)
@@ -3670,17 +3640,11 @@ dumpDatabase(Archive *fout)
 					ii_oid,
 					ii_relminmxid;
 
-		if (fout->remoteVersion >= 90300)
 			appendPQExpBuffer(loFrozenQry, "SELECT relfrozenxid, relminmxid, relfilenode, oid\n"
 							  "FROM pg_catalog.pg_class\n"
 							  "WHERE oid IN (%u, %u, %u, %u);\n",
 							  LargeObjectRelationId, LargeObjectLOidPNIndexId,
 							  LargeObjectMetadataRelationId, LargeObjectMetadataOidIndexId);
-		else
-			appendPQExpBuffer(loFrozenQry, "SELECT relfrozenxid, 0 AS relminmxid, relfilenode, oid\n"
-							  "FROM pg_catalog.pg_class\n"
-							  "WHERE oid IN (%u, %u);\n",
-							  LargeObjectRelationId, LargeObjectLOidPNIndexId);
 
 		lo_res = ExecuteSqlQuery(fout, loFrozenQry->data, PGRES_TUPLES_OK);
 
@@ -4243,10 +4207,6 @@ getPolicies(Archive *fout, TableInfo tblinfo[], int numTables)
 				j,
 				ntups;
 
-	/* No policies before 9.5 */
-	if (fout->remoteVersion < 90500)
-		return;
-
 	/* Skip if --no-policies was specified */
 	if (dopt->no_policies)
 		return;
@@ -4316,10 +4276,7 @@ getPolicies(Archive *fout, TableInfo tblinfo[], int numTables)
 
 	printfPQExpBuffer(query,
 					  "SELECT pol.oid, pol.tableoid, pol.polrelid, pol.polname, pol.polcmd, ");
-	if (fout->remoteVersion >= 100000)
 		appendPQExpBufferStr(query, "pol.polpermissive, ");
-	else
-		appendPQExpBufferStr(query, "'t' as polpermissive, ");
 	appendPQExpBuffer(query,
 					  "CASE WHEN pol.polroles = '{0}' THEN NULL ELSE "
 					  "   pg_catalog.array_to_string(ARRAY(SELECT pg_catalog.quote_ident(rolname) from pg_catalog.pg_roles WHERE oid = ANY(pol.polroles)), ', ') END AS polroles, "
@@ -4534,7 +4491,7 @@ getPublications(Archive *fout)
 	int			i,
 				ntups;
 
-	if (dopt->no_publications || fout->remoteVersion < 100000)
+	if (dopt->no_publications)
 		return;
 
 	query = createPQExpBuffer();
@@ -4897,7 +4854,7 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
 				j,
 				ntups;
 
-	if (dopt->no_publications || fout->remoteVersion < 100000)
+	if (dopt->no_publications)
 		return;
 
 	query = createPQExpBuffer();
@@ -5187,7 +5144,7 @@ getSubscriptions(Archive *fout)
 	int			i,
 				ntups;
 
-	if (dopt->no_subscriptions || fout->remoteVersion < 100000)
+	if (dopt->no_subscriptions)
 		return;
 
 	if (!is_superuser(fout))
@@ -6675,24 +6632,12 @@ getAccessMethods(Archive *fout)
 	query = createPQExpBuffer();
 
 	/*
-	 * Select all access methods from pg_am table.  v9.6 introduced CREATE
-	 * ACCESS METHOD, so earlier versions usually have only built-in access
-	 * methods.  v9.6 also changed the access method API, replacing dozens of
-	 * pg_am columns with amhandler.  Even if a user created an access method
-	 * by "INSERT INTO pg_am", we have no way to translate pre-v9.6 pg_am
-	 * columns to a v9.6+ CREATE ACCESS METHOD.  Hence, before v9.6, read
-	 * pg_am just to facilitate findAccessMethodByOid() providing the
-	 * OID-to-name mapping.
+	 * Select all access methods from pg_am table.
 	 */
 	appendPQExpBufferStr(query, "SELECT tableoid, oid, amname, ");
-	if (fout->remoteVersion >= 90600)
 		appendPQExpBufferStr(query,
 							 "amtype, "
 							 "amhandler::pg_catalog.regproc AS amhandler ");
-	else
-		appendPQExpBufferStr(query,
-							 "'i'::pg_catalog.\"char\" AS amtype, "
-							 "'-'::pg_catalog.regproc AS amhandler ");
 	appendPQExpBufferStr(query, "FROM pg_am");
 
 	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
@@ -6878,15 +6823,12 @@ getAggregates(Archive *fout)
 	int			i_proowner;
 	int			i_aggacl;
 	int			i_acldefault;
+	const char *agg_check;
 
 	/*
 	 * Find all interesting aggregates.  See comment in getFuncs() for the
 	 * rationale behind the filtering logic.
 	 */
-	if (fout->remoteVersion >= 90600)
-	{
-		const char *agg_check;
-
 		agg_check = (fout->remoteVersion >= 110000 ? "p.prokind = 'a'"
 					 : "p.proisagg");
 
@@ -6916,29 +6858,6 @@ getAggregates(Archive *fout)
 								 "refclassid = 'pg_extension'::regclass AND "
 								 "deptype = 'e')");
 		appendPQExpBufferChar(query, ')');
-	}
-	else
-	{
-		appendPQExpBufferStr(query, "SELECT tableoid, oid, proname AS aggname, "
-							 "pronamespace AS aggnamespace, "
-							 "pronargs, proargtypes, "
-							 "proowner, "
-							 "proacl AS aggacl, "
-							 "acldefault('f', proowner) AS acldefault "
-							 "FROM pg_proc p "
-							 "WHERE proisagg AND ("
-							 "pronamespace != "
-							 "(SELECT oid FROM pg_namespace "
-							 "WHERE nspname = 'pg_catalog')");
-		if (dopt->binary_upgrade)
-			appendPQExpBufferStr(query,
-								 " OR EXISTS(SELECT 1 FROM pg_depend WHERE "
-								 "classid = 'pg_proc'::regclass AND "
-								 "objid = p.oid AND "
-								 "refclassid = 'pg_extension'::regclass AND "
-								 "deptype = 'e')");
-		appendPQExpBufferChar(query, ')');
-	}
 
 	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
 
@@ -7021,6 +6940,7 @@ getFuncs(Archive *fout)
 	int			i_prorettype;
 	int			i_proacl;
 	int			i_acldefault;
+	const char *not_agg_check;
 
 	/*
 	 * Find all interesting functions.  This is a bit complicated:
@@ -7039,14 +6959,10 @@ getFuncs(Archive *fout)
 	 * include them, since we want to dump extension members individually in
 	 * that mode.  Also, if they are used by casts or transforms then we need
 	 * to gather the information about them, though they won't be dumped if
-	 * they are built-in.  Also, in 9.6 and up, include functions in
+	 * they are built-in.  Also, include functions in
 	 * pg_catalog if they have an ACL different from what's shown in
 	 * pg_init_privs (so we have to join to pg_init_privs; annoying).
 	 */
-	if (fout->remoteVersion >= 90600)
-	{
-		const char *not_agg_check;
-
 		not_agg_check = (fout->remoteVersion >= 110000 ? "p.prokind <> 'a'"
 						 : "NOT p.proisagg");
 
@@ -7090,46 +7006,6 @@ getFuncs(Archive *fout)
 		appendPQExpBufferStr(query,
 							 "\n  OR p.proacl IS DISTINCT FROM pip.initprivs");
 		appendPQExpBufferChar(query, ')');
-	}
-	else
-	{
-		appendPQExpBuffer(query,
-						  "SELECT tableoid, oid, proname, prolang, "
-						  "pronargs, proargtypes, prorettype, proacl, "
-						  "acldefault('f', proowner) AS acldefault, "
-						  "pronamespace, "
-						  "proowner "
-						  "FROM pg_proc p "
-						  "WHERE NOT proisagg"
-						  "\n  AND NOT EXISTS (SELECT 1 FROM pg_depend "
-						  "WHERE classid = 'pg_proc'::regclass AND "
-						  "objid = p.oid AND deptype = 'i')"
-						  "\n  AND ("
-						  "\n  pronamespace != "
-						  "(SELECT oid FROM pg_namespace "
-						  "WHERE nspname = 'pg_catalog')"
-						  "\n  OR EXISTS (SELECT 1 FROM pg_cast"
-						  "\n  WHERE pg_cast.oid > '%u'::oid"
-						  "\n  AND p.oid = pg_cast.castfunc)",
-						  g_last_builtin_oid);
-
-		if (fout->remoteVersion >= 90500)
-			appendPQExpBuffer(query,
-							  "\n  OR EXISTS (SELECT 1 FROM pg_transform"
-							  "\n  WHERE pg_transform.oid > '%u'::oid"
-							  "\n  AND (p.oid = pg_transform.trffromsql"
-							  "\n  OR p.oid = pg_transform.trftosql))",
-							  g_last_builtin_oid);
-
-		if (dopt->binary_upgrade)
-			appendPQExpBufferStr(query,
-								 "\n  OR EXISTS(SELECT 1 FROM pg_depend WHERE "
-								 "classid = 'pg_proc'::regclass AND "
-								 "objid = p.oid AND "
-								 "refclassid = 'pg_extension'::regclass AND "
-								 "deptype = 'e')");
-		appendPQExpBufferChar(query, ')');
-	}
 
 	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
 
@@ -7378,64 +7254,31 @@ getTables(Archive *fout, int *numTables)
 		appendPQExpBufferStr(query,
 							 "c.relhasoids, ");
 
-	if (fout->remoteVersion >= 90300)
 		appendPQExpBufferStr(query,
 							 "c.relispopulated, ");
-	else
-		appendPQExpBufferStr(query,
-							 "'t' as relispopulated, ");
 
-	if (fout->remoteVersion >= 90400)
 		appendPQExpBufferStr(query,
 							 "c.relreplident, ");
-	else
-		appendPQExpBufferStr(query,
-							 "'d' AS relreplident, ");
 
-	if (fout->remoteVersion >= 90500)
 		appendPQExpBufferStr(query,
 							 "c.relrowsecurity, c.relforcerowsecurity, ");
-	else
-		appendPQExpBufferStr(query,
-							 "false AS relrowsecurity, "
-							 "false AS relforcerowsecurity, ");
 
-	if (fout->remoteVersion >= 90300)
 		appendPQExpBufferStr(query,
 							 "c.relminmxid, tc.relminmxid AS tminmxid, ");
-	else
-		appendPQExpBufferStr(query,
-							 "0 AS relminmxid, 0 AS tminmxid, ");
 
-	if (fout->remoteVersion >= 90300)
 		appendPQExpBufferStr(query,
 							 "array_remove(array_remove(c.reloptions,'check_option=local'),'check_option=cascaded') AS reloptions, "
 							 "CASE WHEN 'check_option=local' = ANY (c.reloptions) THEN 'LOCAL'::text "
 							 "WHEN 'check_option=cascaded' = ANY (c.reloptions) THEN 'CASCADED'::text ELSE NULL END AS checkoption, ");
-	else
-		appendPQExpBufferStr(query,
-							 "c.reloptions, NULL AS checkoption, ");
 
-	if (fout->remoteVersion >= 90600)
 		appendPQExpBufferStr(query,
 							 "am.amname, ");
-	else
-		appendPQExpBufferStr(query,
-							 "NULL AS amname, ");
 
-	if (fout->remoteVersion >= 90600)
 		appendPQExpBufferStr(query,
 							 "(d.deptype = 'i') IS TRUE AS is_identity_sequence, ");
-	else
-		appendPQExpBufferStr(query,
-							 "false AS is_identity_sequence, ");
 
-	if (fout->remoteVersion >= 100000)
 		appendPQExpBufferStr(query,
 							 "c.relispartition AS ispartition ");
-	else
-		appendPQExpBufferStr(query,
-							 "false AS ispartition ");
 
 	/*
 	 * Left join to pg_depend to pick up dependency info linking sequences to
@@ -7453,9 +7296,8 @@ getTables(Archive *fout, int *numTables)
 						 "LEFT JOIN pg_tablespace tsp ON (tsp.oid = c.reltablespace)\n");
 
 	/*
-	 * In 9.6 and up, left join to pg_am to pick up the amname.
+	 * Left join to pg_am to pick up the amname.
 	 */
-	if (fout->remoteVersion >= 90600)
 		appendPQExpBufferStr(query,
 							 "LEFT JOIN pg_am am ON (c.relam = am.oid)\n");
 
@@ -8028,12 +7870,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 						 "t.reloptions AS indreloptions, ");
 
 
-	if (fout->remoteVersion >= 90400)
 		appendPQExpBufferStr(query,
 							 "i.indisreplident, ");
-	else
-		appendPQExpBufferStr(query,
-							 "false AS indisreplident, ");
 
 	if (fout->remoteVersion >= 110000)
 		appendPQExpBufferStr(query,
@@ -8318,10 +8156,6 @@ getExtendedStatistics(Archive *fout)
 	int			i_stattarget;
 	int			i;
 
-	/* Extended statistics were new in v10 */
-	if (fout->remoteVersion < 100000)
-		return;
-
 	query = createPQExpBuffer();
 
 	if (fout->remoteVersion < 130000)
@@ -8988,10 +8822,6 @@ getEventTriggers(Archive *fout)
 				i_evtenabled;
 	int			ntups;
 
-	/* Before 9.3, there are no event triggers */
-	if (fout->remoteVersion < 90300)
-		return;
-
 	query = createPQExpBuffer();
 
 	appendPQExpBufferStr(query,
@@ -9258,10 +9088,6 @@ getTransforms(Archive *fout)
 	int			i_trffromsql;
 	int			i_trftosql;
 
-	/* Transforms didn't exist pre-9.5 */
-	if (fout->remoteVersion < 90500)
-		return;
-
 	query = createPQExpBuffer();
 
 	appendPQExpBufferStr(query, "SELECT tableoid, oid, "
@@ -9489,12 +9315,8 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
 		appendPQExpBufferStr(q,
 							 "'' AS attcompression,\n");
 
-	if (fout->remoteVersion >= 100000)
 		appendPQExpBufferStr(q,
 							 "a.attidentity,\n");
-	else
-		appendPQExpBufferStr(q,
-							 "'' AS attidentity,\n");
 
 	if (fout->remoteVersion >= 110000)
 		appendPQExpBufferStr(q,
@@ -10897,8 +10719,6 @@ getAdditionalACLs(Archive *fout)
 	PQclear(res);
 
 	/* Fetch initial-privileges data */
-	if (fout->remoteVersion >= 90600)
-	{
 		printfPQExpBuffer(query,
 						  "SELECT objoid, classoid, objsubid, privtype, initprivs "
 						  "FROM pg_init_privs");
@@ -10966,7 +10786,6 @@ getAdditionalACLs(Archive *fout)
 			}
 		}
 		PQclear(res);
-	}
 
 	destroyPQExpBuffer(query);
 }
@@ -11141,15 +10960,6 @@ fetchAttributeStats(Archive *fout)
 	static bool restarted;
 	int			max_rels = MAX_ATTR_STATS_RELS;
 
-	/*
-	 * Our query for retrieving statistics for multiple relations uses WITH
-	 * ORDINALITY and multi-argument UNNEST(), both of which were introduced
-	 * in v9.4.  For older versions, we resort to gathering statistics for a
-	 * single relation at a time.
-	 */
-	if (fout->remoteVersion < 90400)
-		max_rels = 1;
-
 	/* If we're just starting, set our TOC pointer. */
 	if (!te)
 		te = AH->toc->next;
@@ -11320,16 +11130,11 @@ dumpRelationStats_dumper(Archive *fout, const void *userArg, const TocEntry *te)
 		 * The results must be in the order of the relations supplied in the
 		 * parameters to ensure we remain in sync as we walk through the TOC.
 		 *
-		 * For v9.4 through v18, the redundant filter clause on s.tablename =
+		 * For versions before 19, the redundant filter clause on s.tablename =
 		 * ANY(...) seems sufficient to convince the planner to use
 		 * pg_class_relname_nsp_index, which avoids a full scan of pg_stats.
 		 * In newer versions, pg_stats returns the table OIDs, eliminating the
 		 * need for that hack.
-		 *
-		 * Our query for retrieving statistics for multiple relations uses
-		 * WITH ORDINALITY and multi-argument UNNEST(), both of which were
-		 * introduced in v9.4.  For older versions, we resort to gathering
-		 * statistics for a single relation at a time.
 		 */
 		if (fout->remoteVersion >= 190000)
 			appendPQExpBufferStr(query,
@@ -11337,7 +11142,7 @@ dumpRelationStats_dumper(Archive *fout, const void *userArg, const TocEntry *te)
 								 "JOIN unnest($1) WITH ORDINALITY AS u (tableid, ord) "
 								 "ON s.tableid = u.tableid "
 								 "ORDER BY u.ord, s.attname, s.inherited");
-		else if (fout->remoteVersion >= 90400)
+		else
 			appendPQExpBufferStr(query,
 								 "FROM pg_catalog.pg_stats s "
 								 "JOIN unnest($1, $2) WITH ORDINALITY AS u (schemaname, tablename, ord) "
@@ -11345,12 +11150,6 @@ dumpRelationStats_dumper(Archive *fout, const void *userArg, const TocEntry *te)
 								 "AND s.tablename = u.tablename "
 								 "WHERE s.tablename = ANY($2) "
 								 "ORDER BY u.ord, s.attname, s.inherited");
-		else
-			appendPQExpBufferStr(query,
-								 "FROM pg_catalog.pg_stats s "
-								 "WHERE s.schemaname = $1[1] "
-								 "AND s.tablename = $2[1] "
-								 "ORDER BY s.attname, s.inherited");
 
 		ExecuteSqlStatement(fout, query->data);
 
@@ -13672,19 +13471,11 @@ dumpFunc(Archive *fout, const FuncInfo *finfo)
 							 "pg_catalog.pg_get_function_result(p.oid) AS funcresult,\n"
 							 "proleakproof,\n");
 
-		if (fout->remoteVersion >= 90500)
 			appendPQExpBufferStr(query,
 								 "array_to_string(protrftypes, ' ') AS protrftypes,\n");
-		else
-			appendPQExpBufferStr(query,
-								 "NULL AS protrftypes,\n");
 
-		if (fout->remoteVersion >= 90600)
 			appendPQExpBufferStr(query,
 								 "proparallel,\n");
-		else
-			appendPQExpBufferStr(query,
-								 "'u' AS proparallel,\n");
 
 		if (fout->remoteVersion >= 110000)
 			appendPQExpBufferStr(query,
@@ -15174,14 +14965,9 @@ dumpCollation(Archive *fout, const CollInfo *collinfo)
 	/* Get collation-specific details */
 	appendPQExpBufferStr(query, "SELECT ");
 
-	if (fout->remoteVersion >= 100000)
 		appendPQExpBufferStr(query,
 							 "collprovider, "
 							 "collversion, ");
-	else
-		appendPQExpBufferStr(query,
-							 "'c' AS collprovider, "
-							 "NULL AS collversion, ");
 
 	if (fout->remoteVersion >= 120000)
 		appendPQExpBufferStr(query,
@@ -15588,7 +15374,6 @@ dumpAgg(Archive *fout, const AggInfo *agginfo)
 							 "pg_catalog.pg_get_function_arguments(p.oid) AS funcargs,\n"
 							 "pg_catalog.pg_get_function_identity_arguments(p.oid) AS funciargs,\n");
 
-		if (fout->remoteVersion >= 90400)
 			appendPQExpBufferStr(query,
 								 "aggkind,\n"
 								 "aggmtransfn,\n"
@@ -15600,31 +15385,12 @@ dumpAgg(Archive *fout, const AggInfo *agginfo)
 								 "aggtransspace,\n"
 								 "aggmtransspace,\n"
 								 "aggminitval,\n");
-		else
-			appendPQExpBufferStr(query,
-								 "'n' AS aggkind,\n"
-								 "'-' AS aggmtransfn,\n"
-								 "'-' AS aggminvtransfn,\n"
-								 "'-' AS aggmfinalfn,\n"
-								 "0 AS aggmtranstype,\n"
-								 "false AS aggfinalextra,\n"
-								 "false AS aggmfinalextra,\n"
-								 "0 AS aggtransspace,\n"
-								 "0 AS aggmtransspace,\n"
-								 "NULL AS aggminitval,\n");
 
-		if (fout->remoteVersion >= 90600)
 			appendPQExpBufferStr(query,
 								 "aggcombinefn,\n"
 								 "aggserialfn,\n"
 								 "aggdeserialfn,\n"
 								 "proparallel,\n");
-		else
-			appendPQExpBufferStr(query,
-								 "'-' AS aggcombinefn,\n"
-								 "'-' AS aggserialfn,\n"
-								 "'-' AS aggdeserialfn,\n"
-								 "'u' AS proparallel,\n");
 
 		if (fout->remoteVersion >= 110000)
 			appendPQExpBufferStr(query,
@@ -17084,8 +16850,6 @@ dumpTable(Archive *fout, const TableInfo *tbinfo)
 			appendPQExpBufferStr(query,
 								 "PREPARE getColumnACLs(pg_catalog.oid) AS\n");
 
-			if (fout->remoteVersion >= 90600)
-			{
 				/*
 				 * In principle we should call acldefault('c', relowner) to
 				 * get the default ACL for a column.  However, we don't
@@ -17110,17 +16874,6 @@ dumpTable(Archive *fout, const TableInfo *tbinfo)
 									 "NOT at.attisdropped "
 									 "AND (at.attacl IS NOT NULL OR pip.initprivs IS NOT NULL) "
 									 "ORDER BY at.attnum");
-			}
-			else
-			{
-				appendPQExpBufferStr(query,
-									 "SELECT attname, attacl, '{}' AS acldefault, "
-									 "NULL AS privtype, NULL AS initprivs "
-									 "FROM pg_catalog.pg_attribute "
-									 "WHERE attrelid = $1 AND NOT attisdropped "
-									 "AND attacl IS NOT NULL "
-									 "ORDER BY attnum");
-			}
 
 			ExecuteSqlStatement(fout, query->data);
 
@@ -19410,16 +19163,10 @@ collectSequences(Archive *fout)
 	const char *query;
 
 	/*
-	 * Before Postgres 10, sequence metadata is in the sequence itself.  With
-	 * some extra effort, we might be able to use the sorted table for those
-	 * versions, but for now it seems unlikely to be worth it.
-	 *
 	 * Since version 18, we can gather the sequence data in this query with
 	 * pg_get_sequence_data(), but we only do so for non-schema-only dumps.
 	 */
-	if (fout->remoteVersion < 100000)
-		return;
-	else if (fout->remoteVersion < 180000 ||
+	if (fout->remoteVersion < 180000 ||
 			 (!fout->dopt->dumpData && !fout->dopt->sequence_data))
 		query = "SELECT seqrelid, format_type(seqtypid, NULL), "
 			"seqstart, seqincrement, "
@@ -19477,59 +19224,20 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	PQExpBuffer delqry = createPQExpBuffer();
 	char	   *qseqname;
 	TableInfo  *owning_tab = NULL;
+	SequenceItem key = {0};
 
 	qseqname = pg_strdup(fmtId(tbinfo->dobj.name));
 
 	/*
-	 * For versions >= 10, the sequence information is gathered in a sorted
+	 * The sequence information is gathered in a sorted
 	 * table before any calls to dumpSequence().  See collectSequences() for
 	 * more information.
 	 */
-	if (fout->remoteVersion >= 100000)
-	{
-		SequenceItem key = {0};
-
 		Assert(sequences);
 
 		key.oid = tbinfo->dobj.catId.oid;
 		seq = bsearch(&key, sequences, nsequences,
 					  sizeof(SequenceItem), SequenceItemCmp);
-	}
-	else
-	{
-		PGresult   *res;
-
-		/*
-		 * Before PostgreSQL 10, sequence metadata is in the sequence itself.
-		 *
-		 * Note: it might seem that 'bigint' potentially needs to be
-		 * schema-qualified, but actually that's a keyword.
-		 */
-		appendPQExpBuffer(query,
-						  "SELECT 'bigint' AS sequence_type, "
-						  "start_value, increment_by, max_value, min_value, "
-						  "cache_value, is_cycled FROM %s",
-						  fmtQualifiedDumpable(tbinfo));
-
-		res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
-
-		if (PQntuples(res) != 1)
-			pg_fatal(ngettext("query to get data of sequence \"%s\" returned %d row (expected 1)",
-							  "query to get data of sequence \"%s\" returned %d rows (expected 1)",
-							  PQntuples(res)),
-					 tbinfo->dobj.name, PQntuples(res));
-
-		seq = pg_malloc0_object(SequenceItem);
-		seq->seqtype = parse_sequence_type(PQgetvalue(res, 0, 0));
-		seq->startv = strtoi64(PQgetvalue(res, 0, 1), NULL, 10);
-		seq->incby = strtoi64(PQgetvalue(res, 0, 2), NULL, 10);
-		seq->maxv = strtoi64(PQgetvalue(res, 0, 3), NULL, 10);
-		seq->minv = strtoi64(PQgetvalue(res, 0, 4), NULL, 10);
-		seq->cache = strtoi64(PQgetvalue(res, 0, 5), NULL, 10);
-		seq->cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0);
-
-		PQclear(res);
-	}
 
 	/* Calculate default limits for a sequence of this type */
 	is_ascending = (seq->incby >= 0);
@@ -19708,8 +19416,6 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 					 tbinfo->dobj.namespace->dobj.name, tbinfo->rolname,
 					 tbinfo->dobj.catId, 0, tbinfo->dobj.dumpId);
 
-	if (fout->remoteVersion < 100000)
-		pg_free(seq);
 	destroyPQExpBuffer(query);
 	destroyPQExpBuffer(delqry);
 	free(qseqname);
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index c1f43113c53..5b10f7122b7 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -693,11 +693,11 @@ main(int argc, char *argv[])
 		fout->verbose = verbose;
 
 		/*
-		 * We allow the server to be back to 9.2, and up to any minor release
+		 * We allow the server to be back to 10, and up to any minor release
 		 * of our own major version.  (See also version check in
 		 * pg_dumpall.c.)
 		 */
-		fout->minRemoteVersion = 90200;
+		fout->minRemoteVersion = 100000;
 		fout->maxRemoteVersion = (PG_VERSION_NUM / 100) * 100 + 99;
 		fout->numWorkers = 1;
 
@@ -954,17 +954,11 @@ dropRoles(PGconn *conn)
 	int			i_rolname;
 	int			i;
 
-	if (server_version >= 90600)
 		printfPQExpBuffer(buf,
 						  "SELECT rolname "
 						  "FROM %s "
 						  "WHERE rolname !~ '^pg_' "
 						  "ORDER BY 1", role_catalog);
-	else
-		printfPQExpBuffer(buf,
-						  "SELECT rolname "
-						  "FROM %s "
-						  "ORDER BY 1", role_catalog);
 
 	res = executeQuery(conn, buf->data);
 
@@ -1041,7 +1035,6 @@ dumpRoles(PGconn *conn)
 	 * Notes: rolconfig is dumped later, and pg_authid must be used for
 	 * extracting rolcomment regardless of role_catalog.
 	 */
-	if (server_version >= 90600)
 		printfPQExpBuffer(buf,
 						  "SELECT oid, rolname, rolsuper, rolinherit, "
 						  "rolcreaterole, rolcreatedb, "
@@ -1052,27 +1045,6 @@ dumpRoles(PGconn *conn)
 						  "FROM %s "
 						  "WHERE rolname !~ '^pg_' "
 						  "ORDER BY 2", role_catalog);
-	else if (server_version >= 90500)
-		printfPQExpBuffer(buf,
-						  "SELECT oid, rolname, rolsuper, rolinherit, "
-						  "rolcreaterole, rolcreatedb, "
-						  "rolcanlogin, rolconnlimit, rolpassword, "
-						  "rolvaliduntil, rolreplication, rolbypassrls, "
-						  "pg_catalog.shobj_description(oid, 'pg_authid') as rolcomment, "
-						  "rolname = current_user AS is_current_user "
-						  "FROM %s "
-						  "ORDER BY 2", role_catalog);
-	else
-		printfPQExpBuffer(buf,
-						  "SELECT oid, rolname, rolsuper, rolinherit, "
-						  "rolcreaterole, rolcreatedb, "
-						  "rolcanlogin, rolconnlimit, rolpassword, "
-						  "rolvaliduntil, rolreplication, "
-						  "false as rolbypassrls, "
-						  "pg_catalog.shobj_description(oid, 'pg_authid') as rolcomment, "
-						  "rolname = current_user AS is_current_user "
-						  "FROM %s "
-						  "ORDER BY 2", role_catalog);
 
 	res = executeQuery(conn, buf->data);
 
-- 
2.50.1 (Apple Git-155)


--6vzYk1swa63ey9i6
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment;
	filename=v3-0002-pg_upgrade-bump-minimum-supported-version-to-v10.patch



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


end of thread, other threads:[~2026-04-17 17:15 UTC | newest]

Thread overview: 461+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-04-17 17:15 [PATCH v3 1/4] pg_dump/pg_dumpall: bump minimum supported version to v10 Nathan Bossart <nathan@postgresql.org>

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